Angular 与 RxJS 之 Async pipe |
您所在的位置:网站首页 › angular ts › Angular 与 RxJS 之 Async pipe |
Angular 中的 observable
在 Angular 中很多概念本质上是一个 observable,比如:
使用 httpClient call API 的返回
使用 ActivatedRoute 获得的 URL params 或者是 queryParams
使用 Angular Form 时需要获得 Form 的变化,valueChanges
使用 Component 中 Output EventEmitter
当然,还有一些概念其实我们也希望它是一个 observable,目前可能还不是,或者支持的还不够好:
Component 中 @Input 的数据变化 (NgChanges => Observable)(其实可以看出,@Input 本质上是一个 Observable, @Output 本质上是一个 Observer,双向绑定本质上是一个 Subject。)
Component 中的 @Input 直接用 Subject 替换 EventEmitter(目前其实两者兼容)。
Component 的生命周期函数 => Observable,比如说,ngDestroy。这样我们就不再需要将逻辑拆分到不同的 生命周期函数中。
Template 中支持的还不够好。
Event to Observable 目前定义起来还比较麻烦。
Angular 中的 Async Pipe
正是因为在 Angular 中很多概念都已经 To Obserable,那在 Template 中如果能够直接使用 Observable 就可以使得逻辑简单很多。举个最简单的例子: 有一个 API /users/:userId 返回 User 的具体信息,User ID 从 URL 中读取后现实具体的信息在页面上。其实逻辑很简单: const user$ = this.activeRoute.params.pipe( distinctUntilChanged(params => params.id), switchMap(id => { return this.service.getUser(id); }), );页面数据的显示也比较简单,直接 subscribe 然后赋值给一个 public 变量就好了。 user$.subscribe(user => this.user = user); {{ user | json}}当然,这里有一些其他的问题,比如你不得不再定义一个 user 变量,比如说你需要多写无脑的 subscribe。比如说,你需要考虑是否需要 unsubscribe. (关于什么时候需要 unsubscribe 我们可以以后单开一篇来讲。) 我们是否可以将那些无脑的 subscribe 和 unsubscribe 和定义重复的变量交给框架层来做呢,答案当然就是 async pipe. {{ user$ | async | json }}如果 template 中其他地方需要使用还可以: {{user?.name}}这样,你不仅不需要定义一个跟 user$ 名字一样的全局变量(当然可以用是否有 $ 来却分 observable 和普通的值)。Angular 本省也会在 component 销毁的时候自动帮你 unsubscribe。省掉了很多重复性的工作。 当然,async 的用法还不止如此,还可以在 *ngFor 或者 component input 中使用,比如: {{user?.name}}或者: 这其实就帮你解决了大部分你需要 subscribe 以后再 bind 到 template 中的情况。 Angular 中的脏检查和 Change Detection熟悉 Angular 或者其前身 AngularJS 的话,对脏检查应该不会陌生。简单的说,当 component 中跟 template bind 的数据变化以后,如何通知页面发生变化呢,比如说: // Template {{user | json}} // Component.ts setTimeout(() => { this.user = {}; }, 3000);当 3 秒钟以后,user 发生变化,Angular 是如何知道 当前 component 的 HTML 要更新呢,答案就是脏检查。最简单的例子就是比如说,每隔一段时间检查一次 component 中所有的字段有没有变化,只要有变化,HTML重新渲染一次。当然真实情况要复杂的多,为了节约资源,Angular 会在任何有可能发生数据变化的情况做检查,@Input 变化,有 click event,setTimeout 之类。 这就是,我们所说的脏检查。 那么,这里跟我们所说的 async 有什么关系呢?虽然在 Angular 中,脏检查已经不再是一件那么耗费性能的事情,也不再需要包裹 JS 中的函数来实现脏检查。但是我们想,Angular 在默认情况下,做的事情永远是尽可能的减少不必要的检查,逼近真实,有没有一种可能性是,当 Template 中的数据发生变化的时候我们主动的通知 Angular,其他情况,Angular 不做检查。 这就是 ChangeDetectionStrategy, onPush 它只在必要的时候,做数据变化的检查,也就是: Input 变化的时候 Event 或者 @Output Event 发生的时候 Async pipe 对应的流 emit value 的时候 主动 Detect changes 的时候这样,就能尽可能的减少脏检查,实现类似 Vue 中的 Push Detect changes。这样其实就有要求,我们在 Template 上原则上只能绑定两类数据: 基于 Input 或者 Event 变化的数据 通过 Async bind 的数据 所以说,Async 另一个重要的功能就是,结合 ChangeDetectionStrategy 实现的摆脱对于 Angular 脏检查的依赖,实现性能的优化。 Async pipe 的实际使用实际上,Angular 对于 observable 只提供了 async pipe,在 template 中使用是非常受限的,也会有很多坑。举个例子: {{user$ | async}}这是一个初接触 async 经常会范的错误,也是容易忽视的问题,因为如果把 user$ 换成普通值是完全没有问题的。但是 流数据 user$ 却完全不同,因为每一次 async pipe, 实际上相当于执行了一次 subscribe,根据之前的文章讲的内容,相同的 observable,subscribe 多次,其实的彼此独立的,举个最简单的例子,假设刚刚例子中的: this.user$ = this.httpClient.get('/user/xxx');你会发现,ajax call 发生的多次。当然解决方法也有很多,你可以将 this.user$.pipe(share()),这样就可以将冷的 observable 变热。 个人比较推荐的做法是:尽可能不在 component 中手动 subscribe 流数据,尽可能不 async pipe 多次同一个流数据。实现的方法其实也恨简单,就是尽可能将 async | pipe 包裹在更外层。比如上面的例子可以这样实现: {{user}}细心的话可能就发现了另外一个问题,这两者并不完全等价,在 user$ emit value 前,包裹的部分会处于 ngIf flase 中,也就是 HTML 会消失,这常常并不是我们的本意。我们的目的只是为了要 async pipe 流数据,但是却并不想让它包裹的范围影响显示。本质上我们需要利用的是变量的 scope 范围。 遗憾的是,Angular 中并没有除了,*ngIf, *ngFor, @Input pipe 之外的方法。当然,简单写个 hack 就可以实现了,比如说,我们让 ngIf 永远不是 false。最简单的就是将我们的值包裹在一个 object 中,这样最差的情况也是 {},从而实现非空。举个例子: {{state.user}}当然,你也可以使用 ngrx/component 中提供的 ngrxLet,看起来会更加简洁: {{user}} 所以说,第二个经验就是:如果你只想使用流数据并不想影响 HTML,你可以使用非 false 的 *ngIf 或者使用ngrxLet。当然,这不可能是最后一个坑,还有很多其他的坑,在实际的使用中,我们往往不会只有一个 流数据,而且 template 中使用起来可能错综复杂。而且,*ngIf是不能在同一个 HTML 标签上 apply 两次的。这里就涉及到处理复杂 async pipe 的三种方法: 利用 ng-container 不占用 HTML 位置的特性: xxxx当然,这样写,无疑让简单的代码看起来更加复杂了,虽然最后生成的 HTML 还是:xxxx 利用 *ngIf 实现多个数据流的包裹: xxxx实际上,我们可以总结出一个类似套路的解法,就是在 component 的最外层,包裹一层 ng-container,作为 component state 来处理所有的数据。 当然,还有一个类似的套路就是在 component 中将所有的 流数据 通过 combineLatest 包裹起来,从而实现类似的效果。(当然,这里并不完全一致,我们先不说)。当然,个人认为最方便的还是通过实现一个简单的 state 方法来实现。 虽然,*ngIf 不能一行写多个,但是 component 完全支持多个 input 的 async pipe, 所以,尽可能将 component 拆的细致,从而通过 component 的 input 实现多个 async pipe 也是一种方法。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |